Solved
booltje124

May 2nd, 2025 07:57 AM

Hi!

I've deployed my SaaS application built with Wave, but I'm running into a routing error when using Volt.

The URL in question is:

http://mywebsite.com/projects/9ed0a5f0-7d9b-4609-b166-94490a4b9a98

The error I'm getting is:

Cannot assign string to property Livewire\Volt\Component@anonymous::$project of type App\Models\Project

    @volt('project')

I'm using UUIDs for the id field, and this works perfectly fine in my local environment. The model uses the HasUuids trait, and the migration defines the primary key like this:

$table->uuid('id')->primary();

What could be causing this issue in production?

bobbyiliev

May 4th, 2025 09:36 PM

Hey!

I just tested this and it seems to be working for me:

Would you mind sharing your exact volt component? Also what database are you using in production?

booltje124

May 5th, 2025 01:26 AM

Hi Bobby,

Thanks for your response!

On production i am using mysql instead of sqlite.

<?php

use function Laravel\Folio\{middleware, name};
use Livewire\Volt\Component;
use App\Models\Project;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Infolists\Concerns\InteractsWithInfolists;
use Filament\Forms\Contracts\HasForms;
use Filament\Infolists\Contracts\HasInfolists;
use Filament\Infolists\Infolist;
use Filament\Infolists\Components\Split;
use Filament\Infolists\Components\Section;
use Filament\Infolists\Components\Tabs;
use Filament\Infolists\Components\ImageEntry;
use Filament\Infolists\Components\TextEntry;
use App\Infolists\Components\ProjectProgressBar;
use Filament\Infolists\Components\Fieldset;
use Filament\Forms\Components\DatePicker;
use Filament\Infolists\Components\Actions\Action;
use Filament\Forms\Components\Radio;
use App\Models\Token;
use App\Models\DealToken;
use Filament\Notifications\Notification;
use Filament\Actions\EditAction;
use Filament\Actions\Contracts\HasActions;
use Filament\Actions\Concerns\InteractsWithActions;
use App\Actions\CalculateDealUnlock;
use Filament\Actions\DeleteAction;
use App\Livewire\UnlockTable;
use Livewire\Attributes\On;
use App\Livewire\DealsTable;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;

middleware('auth');
name('project');

new class extends Component implements HasForms, HasInfolists, HasActions {

    use InteractsWithInfolists;
    use InteractsWithForms;
    use InteractsWithActions;
    use AuthorizesRequests;

    public Project $project;

    public bool $isMarketDataLinked = false;

    public function mount(Project $project)
    {

        $this->authorize('view', $project);

        $this->project = $project;

        if ($this->project->token) {
            $this->isMarketDataLinked = true;
        }
    }

    public function editAction(): EditAction
    {
        return EditAction::make()
            ->hiddenLabel()
            ->button()
            ->icon('heroicon-m-pencil-square')
            ->record($this->project)
            ->mutateFormDataUsing(function ($data) {
                $data['ticker'] = Str::upper($data['ticker']);

                $data['name'] = Str::ucfirst(Str::lower($data['name']));

                return $data;
            })
            ->form(Project::getForm());
    }

    public function deleteAction(): DeleteAction
    {
        return DeleteAction::make()
            ->record($this->project)
            ->hiddenLabel()
            ->button()
            ->color('danger')
            ->icon('heroicon-m-trash')
            ->successRedirectUrl(route('projects'))
            ->requiresConfirmation();
    }

    #[on('refreshParent')]
    public function productInfolist(Infolist $infolist): Infolist
    {
        return $infolist
            ->record($this->project)
            ->schema([
                Split::make([
                    Section::make('project_information')
                        ->extraAttributes(['class' => 'h-full'])
                        ->heading('Project information')
                        ->schema([
                            ImageEntry::make('image')
                                ->label(false)
                                ->alignCenter()
                                ->circular()
                                ->size(150),
                            TextEntry::make('name')
                                ->label(false)
                                ->alignCenter()
                                ->size(TextEntry\TextEntrySize::Large),
                            ProjectProgressBar::make('unlock_percentage')
                                ->label(false)
                                ->default(function () {
                                    $totalTokens = $this->project->unlocks->sum('token_amount');
                                    $unlockedTokens = $this->project->unlocks->where('unlock_date', '<',
                                        today())->sum('token_amount');

                                    return $totalTokens > 0
                                        ? round(($unlockedTokens / $totalTokens) * 100, 2)
                                        : 0;

                                }),
                        ]),
                    Section::make('my_contribution')
                        ->extraAttributes(['class' => 'h-full'])
                        ->heading('My contribution')
                        ->schema([
                            \Filament\Infolists\Components\Livewire::make(\App\Livewire\ContributionTable::class,
                                ['project' => $this->project])->key('contribution-table')
                        ]),
                    Section::make('market_information')
                        ->extraAttributes(['class' => 'h-full'])
                        ->heading('Current Market Data')
                        ->headerActions([
                            Action::make('link')
                                ->color('warning')
                                ->icon('heroicon-m-link')
                                ->fillForm(fn(Project $record): array => [
                                    'token' => $record->token->id ?? null,
                                ])
                                ->form([
                                    Radio::make('token')
                                        ->required()
                                        ->options(function ($record) {
                                            return Token::where('name', $record->name)->orWhere('ticker',
                                                $record->ticker)->pluck('name', 'id')->toArray();
                                        })
                                ])
                                ->action(function (Project $record, array $data, array $arguments) {

                                    if ($arguments['unlink'] ?? false) {
                                        DealToken::where('project_id', $this->project->id)->firstorFail()->delete();
                                        return Notification::make()
                                            ->title('Token unlinked!')
                                            ->success()
                                            ->send();
                                    }

                                    DealToken::updateOrCreate(
                                        ['project_id' => $record->id],
                                        ['token_id' => $data['token']]
                                    );

                                    return Notification::make()
                                        ->title('Token saved!')
                                        ->success()
                                        ->send();

                                })
                                ->after(function () {
                                    $this->dispatch('refresh-data')->to(UnlockTable::class);
                                })
                                ->modalHeading('Choose token')
                                ->extraModalFooterActions(fn(Action $action): array => [
                                    $action->makeModalSubmitAction('unlink', arguments: ['unlink' => true]),
                                ]),

                        ])
                        ->schema([
                            Fieldset::make('market_data')
                                ->label(false)
                                ->schema([
                                    TextEntry::make('token.ticker')
                                        ->label('Ticker')
                                        ->placeholder('-'),
                                    TextEntry::make('token.price')
                                        ->formatStateUsing(function($state){
                                            if((float)$state >= 0.01){
                                                return round($state,3);
                                            } else {
                                                return $state;
                                            }
                                        })
                                        ->label('Price')
                                        ->prefix('$')
                                        ->placeholder('-'),
                                    TextEntry::make('project_roi')
                                        ->default(function(){
                                            return round((($this->project->token->price - $this->project->averageBuyPrice()) / $this->project->averageBuyPrice()) * 100,2);
                                        })
                                        ->prefix(function ($state) {
                                            if ($state > 0) {
                                                return '+';
                                            }
                                        })
                                        ->color(function ($state) {
                                            if ($state < 0) {
                                                return 'danger';
                                            } else {
                                                return 'success';
                                            }
                                        })
                                        ->suffix('%')
                                        ->label('Average price roi')
                                        ->placeholder('-'),
                                    TextEntry::make('token.ticker')
                                        ->hidden(function () {
                                            return $this->isMarketDataLinked;
                                        })
                                        ->label('Ticker')
                                        ->placeholder('-'),
                                    TextEntry::make('token.day_change')
                                        ->formatStateUsing(function ($state) {
                                            return round($state, 2);
                                        })
                                        ->prefix(function ($state) {
                                            if ($state > 0) {
                                                return '+';
                                            }
                                        })
                                        ->color(function ($state) {
                                            if ($state < 0) {
                                                return 'danger';
                                            } else {
                                                return 'success';
                                            }
                                        })
                                        ->suffix('%')
                                        ->label('24H change')
                                        ->placeholder('-'),
                                    TextEntry::make('token.atl')
                                        ->label('All time high')
                                        ->money()
                                        ->placeholder('-'),
                                    TextEntry::make('token.atl')
                                        ->label('All time low')
                                        ->formatStateUsing(function ($state) {
                                            return round($state, 2);
                                        })
                                        ->prefix(function ($state) {
                                            if ($state > 0) {
                                                return '+';
                                            }
                                        })
                                        ->color(function ($state) {
                                            if ($state < 0) {
                                                return 'danger';
                                            } else {
                                                return 'success';
                                            }
                                        })
                                        ->suffix('%')
                                        ->placeholder('-'),
                                ])->columns(3),
                            Fieldset::make('tge_date')
                                ->label(false)
                                ->schema([
                                    TextEntry::make('tge_date')
                                        ->tooltip('Token generation event')
                                        ->badge()
                                        ->default('')
                                        ->placeholder('-')
                                        ->suffixAction(
                                            Action::make('setTgeDate')
                                                ->label(function ($record) {
                                                    if ($record->tge_date) {
                                                        return 'Edit';
                                                    } else {
                                                        return 'Add';
                                                    }
                                                })
                                                ->button()
                                                ->fillForm(fn(Project $record): array => [
                                                    'tge_date' => $record->tge_date,

                                                ])
                                                ->form([
                                                    DatePicker::make('tge_date')
                                                        ->required()
                                                ])
                                                ->action(function (Project $record, $data) {
                                                    $record->tge_date = $data['tge_date'];
                                                    $record->save();

                                                    if ($record->wasChanged('tge_date')) {
                                                        $this->regenerateUnlock();
                                                    }

                                                    return Notification::make()
                                                        ->title('Tge date saved!')
                                                        ->success()
                                                        ->send();


                                                })
                                                ->modalHeading('Set Tge date')
                                                ->requiresConfirmation()
                                                ->after(function () {
                                                    $this->dispatch('refresh-data')->to(UnlockTable::class);
                                                    $this->dispatch('refresh-data')->to(DealsTable::class);
                                                })

                                        )->columnSpanFull()
                                ])
                        ])
                ])->columns(3)->extraAttributes(['class' => 'items-stretch']),
                Split::make([
                    Section::make('deals')
                        ->heading(false)
                        ->schema([
                            Tabs::make('Tabs')
                                ->tabs([
                                    Tabs\Tab::make('Deals')
                                        ->badge(function (Project $record) {
                                            return $record->countDeals();
                                        })
                                        ->icon('phosphor-handshake-fill')
                                        ->schema([
                                            \Filament\Infolists\Components\Livewire::make(DealsTable::class,
                                                ['project' => $this->project])->key('deals-table')
                                        ]),
                                    Tabs\Tab::make('Sell Orders')
                                        ->badge(function (Project $record) {
                                            return $record->sellOrders()->count();
                                        })
                                        ->icon('heroicon-m-currency-dollar')
                                        ->schema([
                                            \Filament\Infolists\Components\Livewire::make(\App\Livewire\SellOrdersTable::class,
                                                ['project' => $this->project])->key('sell-orders-table')
                                        ]),
                                    Tabs\Tab::make('Profit Overview')
                                        ->icon('heroicon-m-chart-bar')
                                        ->schema([
//                                            \Filament\Infolists\Components\Livewire::make(\App\Livewire\ProjectProfitOverview::class,
//                                                ['project' => $this->project])->key('profit-overview-widget')
                                        ]),
                                ])->contained(false)
                        ])
                        ->columnSpan(2),

                    Section::make('unlock_schedule')
                        ->heading('$'.$this->project->ticker.' unlock schedule')
//                        ->headerActions([
//                            \Filament\Infolists\Components\Actions\Action::make('manage_unlocks')
//                                ->label('Manage')
//                        ])
                        ->schema([
                            \Filament\Infolists\Components\Livewire::make(UnlockTable::class,
                                ['project' => $this->project])->key('unlock-table')
                        ])
                        ->grow(false)
                        ->columnSpan(1),
                ]),
            ]);
    }

    public function regenerateUnlock(): void
    {
        $createUnlocks = new CalculateDealUnlock();

        foreach ($this->project->deals as $deal) {
            $createUnlocks->handle($deal);
            \App\Actions\CheckDealStatus::handle($deal);
        }

    }


}

?>

<x-layouts.app>
    @volt('project')
    <div>
        <div class="px-2 flex justify-between items-center ">
            <nav class="flex" aria-label="Breadcrumb">
                <ol role="list" class="flex items-center space-x-4">
                    <li>
                        <div>
                            <a href="/dashboard" class="text-gray-400 hover:text-gray-500">
                                <svg class="h-5 w-5 flex-shrink-0" viewBox="0 0 20 20" fill="currentColor"
                                     aria-hidden="true">
                                    <path fill-rule="evenodd"
                                          d="M9.293 2.293a1 1 0 011.414 0l7 7A1 1 0 0117 11h-1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-3a1 1 0 00-1-1H9a1 1 0 00-1 1v3a1 1 0 01-1 1H5a1 1 0 01-1-1v-6H3a1 1 0 01-.707-1.707l7-7z"
                                          clip-rule="evenodd"/>
                                </svg>
                                <span class="sr-only">Home</span>
                            </a>
                        </div>
                    </li>
                    <li>
                        <div class="flex items-center">
                            <svg class="h-5 w-5 flex-shrink-0 text-gray-400" viewBox="0 0 20 20" fill="currentColor"
                                 aria-hidden="true">
                                <path fill-rule="evenodd"
                                      d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z"
                                      clip-rule="evenodd"/>
                            </svg>
                            <a href="/projects"
                               class="ml-4 text-sm font-medium text-gray-500 hover:text-gray-700">Projects</a>
                        </div>
                    </li>
                    <li>
                        <div class="flex items-center">
                            <svg class="h-5 w-5 flex-shrink-0 text-gray-400" viewBox="0 0 20 20" fill="currentColor"
                                 aria-hidden="true">
                                <path fill-rule="evenodd"
                                      d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z"
                                      clip-rule="evenodd"/>
                            </svg>
                            <a href="#" class="ml-4 text-sm font-medium text-gray-500 hover:text-gray-700"
                               aria-current="page">{{ $this->project->name }}</a>
                        </div>
                    </li>
                </ol>
            </nav>

            <div class="ml-auto flex items-center space-x-4">
                {{ $this->editAction() }}
                {{ $this->deleteAction() }}
            </div>

        </div>

        <div class="mt-5 pb-10">
            {{ $this->productInfolist }}
            <x-filament-actions::modals/>
        </div>
    </div>

    @endvolt
</x-layouts.app>


booltje124

May 5th, 2025 06:21 AM

Switched to a livewire component, but same error

IMG_6199.jpeg

Maybe a folio error?

bobbyiliev

May 7th, 2025 05:52 AM

Hey there! 👋

It looks like Folio is passing a string (maybe the project ID or slug?) to your Livewire component, but your $project property is typed as a Project model, which is why you're seeing that type error.

If I remember correctly, Folio doesn't do route model binding out of the box (at least not like standard Laravel routes), you might need to either:

  1. Load the model manually in your component, like so:
public string $project;
public Project $loadedProject;

public function mount()
{
    $this->loadedProject = Project::findOrFail($this->project);
}
  1. Or resolve the model in your Blade file before passing it in:
<?php
use App\Models\Project;

$projectModel = Project::findOrFail($project);
?>

@livewire('view-project', ['project' => $projectModel])

Not 100% sure without seeing how you're passing the parameter in, but this should be pretty close. If you're using a slug instead of an ID, you'd just want to swap in where('slug', $this->project)->firstOrFail() instead.

booltje124

May 7th, 2025 11:00 PM

thanks! after my vacation i will test it and let you know.

booltje124

May 16th, 2025 07:27 AM

I cant seem to get in work on production. Local everything is working fine. So route model binding also works. Switched back to a Volt component, like in my first post.

I am using an Uuid as parameter:

http://mywebsite.com/projects/9ed0a5f0-7d9b-4609-b166-94490a4b9a98

Attached the full error page.

Error.png

booltje124

May 21st, 2025 02:09 AM

Can someone help me please?

bobbyiliev

May 21st, 2025 06:37 AM

Best Answer

Hey!

I've not been able to reproduce the issue on my end.

I just tested this with the exact same setup you're using — including:

  • resources/views/projects/[project].blade.php using an inline Volt component
  • Route model binding via public Project $project and mount(Project $project)
  • UUIDs via HasUuids in the model
  • MySQL with uuid('id')->primary() in the migration

It all works great on MySQL in production-like conditions.

Here's the minimal setup I used:


resources/views/projects/[project].blade.php

<?php

use App\Models\Project;
use Livewire\Volt\Component;
use function Laravel\Folio\{middleware, name};

middleware('auth');
name('project');

new class extends Component {
    public Project $project;

    public function mount(Project $project)
    {
        $this->project = $project;
    }
}
?>

<x-layouts.app>
    @volt('project')
        <div class="p-6">
            <h2 class="text-2xl font-bold">Project Details</h2>
            <p class="mt-2 text-gray-700">Name: {{ $this->project->name }}</p>
            <p class="text-sm text-gray-500">ID: {{ $this->project->id }}</p>
            <a href="/projects" class="mt-4 inline-block text-blue-600 underline">← Back to Projects</a>
        </div>
    @endvolt
</x-layouts.app>

Project Model

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Concerns\HasUuids;

class Project extends Model
{
    use HasUuids;

    protected $fillable = ['name'];
}

Migration

Schema::create('projects', function (Blueprint $table) {
    $table->uuid('id')->primary();
    $table->string('name');
    $table->timestamps();
});

If it's still not working for you in production, could you share a minimal reproducible version that I can test with?

Without being able to reproduce the issue, it is difficult to say what might be going wrong.